// Copyright (c) Huawei Technologies Co., Ltd. 2022-2022. All rights reserved.

// 弹性伸缩策略服务
package policy

import (
	"fleetmanager/api/errors"
	FleetModel "fleetmanager/api/model/fleet"
	"fleetmanager/api/model/policy"
	policymodel "fleetmanager/api/model/policy"
	"fleetmanager/api/params"
	"fleetmanager/api/service/base"
	"fleetmanager/api/service/constants"
	"fleetmanager/api/service/fleet"
	"fleetmanager/client"
	"fleetmanager/db/dao"
	"fleetmanager/logger"
	"fleetmanager/utils"
	"fmt"
	"github.com/beego/beego/v2/client/orm"
	"github.com/beego/beego/v2/server/web/context"
	"net/http"
	"sync"
)

type Service struct {
	base.FleetService
	projectId     string
	createReq     *policy.CreateRequest
	updateReq     *policy.UpdateRequest
	scalingPolicy *dao.ScalingPolicy
	createRsp     *policy.CreateResponseFromAASS
}

// NewPolicyService 新建fleet policy服务
func NewPolicyService(ctx *context.Context, logger *logger.FMLogger) *Service {
	s := &Service{
		FleetService: base.FleetService{
			Ctx:    ctx,
			Logger: logger,
		},
	}
	return s
}

// 设置policy对象
func (s *Service) SetPolicyById(policyId string) *errors.CodedError {
	p, err := dao.GetScalingPolicyStorage().Get(dao.Filters{"Id": policyId})
	if err != nil {
		s.Logger.Error("get policy from db error: %v", err)
		if err == orm.ErrNoRows {
			return errors.NewError(errors.PolicyNotFound)
		}
		return errors.NewError(errors.DBError)
	}

	s.scalingPolicy = p
	return nil
}

// 绑定fleet和伸缩策略
func (s *Service) Bind() (code int, rsp []byte, e *errors.CodedError) {
	policyId := s.Ctx.Input.Param(params.PolicyId)
	fleetId := s.Ctx.Input.Param(params.FleetId)
	if len(fleetId) == 0 || len(policyId) == 0 {
		s.Logger.Error("fleetId [%s] or policyId [%s] is invalid", fleetId, policyId)
		return http.StatusBadRequest, nil, errors.NewError(errors.ServerInternalError)
	}
	code, rsp, err := s.SendBindToAASS(policyId, fleetId)
	if err != nil {
		return 0, nil, errors.NewErrorF(errors.ServerInternalError, fmt.Sprintf("error:%+v", err))
	}
	code, rsp, e = s.ForwardRspCheck(code, rsp, err)
	if code < http.StatusOK || code >= http.StatusBadRequest {
		s.Logger.Error("resp from aass with error code:%d, rsp:[%s]", code, string(rsp))
		return
	}
	return
}

// 解绑fleet和伸缩策略
func (s *Service) UnBind() (code int, rsp []byte, e *errors.CodedError) {
	policyId := s.Ctx.Input.Param(params.PolicyId)
	fleetId := s.Ctx.Input.Param(params.FleetId)
	if len(fleetId) == 0 || len(policyId) == 0 {
		s.Logger.Error("fleetId [%s] or policyId [%s] is invalid", fleetId, policyId)
		return http.StatusBadRequest, nil, errors.NewError(errors.ServerInternalError)
	}
	code, rsp, err := s.SendUnBindToAASS(policyId, fleetId)
	if err != nil {
		return 0, nil, errors.NewErrorF(errors.ServerInternalError, fmt.Sprintf("error:%+v", err))
	}
	code, rsp, e = s.ForwardRspCheck(code, rsp, err)
	if code < http.StatusOK || code >= http.StatusBadRequest {
		s.Logger.Error("resp from aass with error code:%d, rsp:[%+v]", code, rsp)
		return
	}
	return
}

func (s *Service) SendBindToAASS(policyId, fleetId string) (code int, rsp []byte, err error) {
	projectId := s.Ctx.Input.Param(params.ProjectId)
	u, err := dao.GetUserResConfByOriProjectId(projectId)
	if err != nil {
		s.Logger.Error("get user res conf error: %v", err)
		return 0, nil, err
	}
	url := client.GetServiceEndpoint(client.ServiceNameAASS, u.Region) +
		fmt.Sprintf(constants.BindScalingPolicyPattern, u.OriProjectId, policyId)
	req := client.NewRequest(client.ServiceNameAASS, url, http.MethodPut, nil)
	req.SetHeader(map[string]string{
		logger.RequestId: fmt.Sprintf("%s", s.Ctx.Input.GetData(logger.RequestId)),
	})
	req.SetQuery(params.QueryFleetId, fleetId)
	return req.DoRequest()
}

func (s *Service) SendUnBindToAASS(policyId, fleetId string) (code int, rsp []byte, err error) {
	projectId := s.Ctx.Input.Param(params.ProjectId)
	u, err := dao.GetUserResConfByOriProjectId(projectId)
	if err != nil {
		s.Logger.Error("get user res conf error: %v", err)
		return 0, nil, err
	}
	url := client.GetServiceEndpoint(client.ServiceNameAASS, u.Region) +
		fmt.Sprintf(constants.BindScalingPolicyPattern, u.OriProjectId, policyId)
	req := client.NewRequest(client.ServiceNameAASS, url, http.MethodDelete, nil)
	req.SetHeader(map[string]string{
		logger.RequestId: fmt.Sprintf("%s", s.Ctx.Input.GetData(logger.RequestId)),
	})
	req.SetQuery(params.QueryFleetId, fleetId)
	return req.DoRequest()
}

// build一个返回体
func (s *Service) GetBindRelationByFleetId() (*policymodel.PolicyListResp, *errors.CodedError) {
	fleetId := s.Ctx.Input.Query(params.FleetId)
	if len(fleetId) == 0 {
		s.Logger.Error("the policy id is invalid")
		return nil, errors.NewErrorF(errors.ServerInternalError, fmt.Sprintf("the policy id is invalid"))
	}
	code, rsp, err := s.getRelationFromAASS(fleetId, "")
	s.Logger.Info("update policy to aass, code: %d, rsp: %s, error: %+v", code, rsp, err)
	respMsg := &policymodel.ListRelationResp{}
	codeErr := base.AcceptResp(s.Logger, &respMsg, code, rsp, err)
	if codeErr != nil {
		s.Logger.Error("get bind relation by fleet_id [%s] err:%+v", fleetId, codeErr)
		return nil, codeErr
	}
	policyList := policymodel.PolicyListResp{}
	for _, rel := range respMsg.Relation {
		onePolicy, err := s.GetPolicy(rel.ScalingPolicyId)
		if err != nil {
			s.Logger.Error("get policy [%s] error:%+v", rel.ScalingPolicyId, err)
			continue
		}
		policyList.AssociatedPolicyOfFleetList = append(policyList.AssociatedPolicyOfFleetList, *onePolicy)
	}
	policyList.Count = len(policyList.AssociatedPolicyOfFleetList)
	return &policyList, nil
}

func (s *Service) GetBindRelationByPolicyId() (*policymodel.FleetListResp, *errors.CodedError) {
	policyId := s.Ctx.Input.Param(params.PolicyId)
	if len(policyId) == 0 {
		s.Logger.Error("the policy id is invalid")
		return nil, errors.NewErrorF(errors.ServerInternalError, fmt.Sprintf("the policy id is invalid"))
	}
	code, rsp, err := s.getRelationFromAASS("", policyId)
	s.Logger.Info("update policy to aass, code: %d, rsp: %s, error: %+v", code, rsp, err)
	respMsg := &policymodel.ListRelationResp{}
	codeErr := base.AcceptResp(s.Logger, &respMsg, code, rsp, err)
	if codeErr != nil {
		s.Logger.Error("get bind relation byu policy_id [%s] err:%+v", policyId, codeErr)
		return nil, codeErr
	}
	resp, errMsg := s.BuildFleetResponse(respMsg)
	if errMsg != nil {
		s.Logger.Error("build response of associated fleet of policy [%s] error:%+v", policyId, errMsg)
		return nil, errMsg
	}
	return resp, nil
}

func (s *Service) BuildFleetResponse(relation *policymodel.ListRelationResp) (*policymodel.FleetListResp, *errors.CodedError) {
	fleetService := fleet.NewFleetService(s.Ctx, s.Logger)
	AssociatedFleetOfPolicy := []policymodel.AssociatedFleetOfPolicy{}
	wg := sync.WaitGroup{}
	wg.Add(len(relation.Relation))
	for _, rel := range relation.Relation {
		ft, err := dao.GetFleetStorage().Get(dao.Filters{"Id": rel.FleetId})
		if err != nil {
			return nil, errors.NewError(errors.DBError)
		}
		go func(oneFt FleetModel.Fleet) {
			defer wg.Done()
			mftp, err := fleetService.GetMonitorFleet(&oneFt)
			if err != nil {
				s.Logger.Info("get fleet: %s info error: %+v", oneFt.FleetId, err)
			} else {
				AssociatedFleetOfPolicy = append(AssociatedFleetOfPolicy, GenerateAssociatedFleetRspOfPolicy(*mftp))
			}
		}(fleet.BuildFleetModel(ft))
	}
	wg.Wait()
	fleetListResp := policymodel.FleetListResp{
		Count:                       len(AssociatedFleetOfPolicy),
		AssociatedFleetOfPolicyList: AssociatedFleetOfPolicy,
	}
	return &fleetListResp, nil
}

func GenerateAssociatedFleetRspOfPolicy(ft FleetModel.ShowMonitorFleetResponse) policymodel.AssociatedFleetOfPolicy {
	return policymodel.AssociatedFleetOfPolicy{
		Name:                ft.Name,
		FleetId:             ft.FleetId,
		State:               ft.State,
		InstanceCount:       ft.InstanceCount,
		ProcessCount:        ft.ProcessCount,
		ServerSessionCount:  ft.ServerSessionCount,
		MaxServerSessionNum: ft.MaxServerSessionNum,
	}
}

// 检索绑定的所有fleet关系
func (s *Service) getRelationFromAASS(fleetId, policyId string) (code int, rsp []byte, err error) {
	projectId := s.Ctx.Input.Param(params.ProjectId)
	u, err := dao.GetUserResConfByOriProjectId(projectId)
	if err != nil {
		s.Logger.Error("get user res conf error: %v", err)
		return 0, nil, err
	}
	url := client.GetServiceEndpoint(client.ServiceNameAASS, u.Region) +
		fmt.Sprintf(constants.CreateScalingPolicyUrl, projectId)
	req := client.NewRequest(client.ServiceNameAASS, url, http.MethodGet, nil)
	req.SetHeader(map[string]string{
		logger.RequestId: fmt.Sprintf("%s", s.Ctx.Input.GetData(logger.RequestId)),
	})
	if len(fleetId) != 0 {
		req.SetQuery(params.QueryFleetId, fleetId)
	}
	if len(policyId) != 0 {
		req.SetQuery(params.QueryPolicyId, policyId)
	}
	req.SetQuery(params.QueryOffset,
		utils.GetStringIfNotEmpty(s.Ctx.Input.Query(params.QueryOffset), params.DefaultOffset))
	req.SetQuery(params.QueryLimit, utils.GetStringIfNotEmpty(s.Ctx.Input.Query(params.QueryLimit), params.DefaultLimit))

	return req.DoRequest()
}

func (s *Service) convertRelation(relationList *policymodel.ListRelationResp) {
	fleetStorage := dao.GetFleetStorage()
	policyStorage := dao.GetScalingPolicyStorage()
	for index, _ := range relationList.Relation {
		fleet, err := fleetStorage.Get(dao.Filters{"Id": relationList.Relation[index].FleetId})
		if err != nil {
			s.Logger.Error("get fleet [%s] error:%+v", relationList.Relation[index].FleetId, err)
		} else {
			relationList.Relation[index].FleetName = fleet.Name
		}
		policy, err := policyStorage.Get(dao.Filters{"Id": relationList.Relation[index].ScalingPolicyId})
		if err != nil {
			s.Logger.Error("get fleet [%s] error:%+v", relationList.Relation[index].FleetId, err)
		} else {
			relationList.Relation[index].ScalingPolicyName = policy.Name
		}
	}
}
